#include <iostream>
#include <atomic>
#include <chrono>

#include <sys/socket.h>

#include "clientservice.hpp"
#include "public.hpp"


using std::cout;
using std::cerr;
using std::endl;
using std::cin;



//单例对象
ClientService* ClientService::getInstacnce(){
    static ClientService client_service;
    return &client_service;
}

//设置连接对象
void ClientService::setTcpConnectionPtr(const TcpConnectionPtr& conn){
    if(conn->connected()){
        this->_conn=conn;
    }
}

ClientService::ClientService(){

    using boost::bind;
    using namespace boost::placeholders;

    //注册主菜单界面的函数
    mainMenuHandler.insert({"1",bind(&ClientService::login,this)});
    mainMenuHandler.insert({"2",bind(&ClientService::reg,this)});
    mainMenuHandler.insert({"3",bind(&ClientService::quit,this)});

    /**
     * * 注册聊天界面功能绑定函数
     * {"help", help},
     * {"chat", chat},
     * {"addfriend", addfriend},
     * {"creategroup", creategroup},
     * {"addgroup", addgroup},
     * {"groupchat", groupchat},
     * {"loginout", loginout}};
    */
   
   commandHandlerMap.insert({"help",bind(&ClientService::help,this,_1)});
   commandHandlerMap.insert({"chat",bind(&ClientService::chat,this,_1)});
   commandHandlerMap.insert({"addfriend",bind(&ClientService::addfriend,this,_1)});
   commandHandlerMap.insert({"creategroup",bind(&ClientService::creategroup,this,_1)});
   commandHandlerMap.insert({"addgroup",bind(&ClientService::addgroup,this,_1)});
   commandHandlerMap.insert({"groupchat",bind(&ClientService::groupchat,this,_1)});
   commandHandlerMap.insert({"loginout",bind(&ClientService::loginout,this,_1)});
}

//客户端的主线程，负责用户输入逻辑处理等
void ClientService::mainloop(){
    
    
    cout<<_conn->name()<<endl;
    cout<<_conn.get()<<endl;

    //初始化信号量
    sem_init(&rwsem,0,0);

    //设置主循环运行
    isMainLoopRun=true;

    while(isMainLoopRun){
        //显示登录注册退出功能菜单
        showLoginMenu();

        string input;
        cin>>input;
        cin.ignore(255,'\n');
        auto iter = mainMenuHandler.find(input);
        if(iter==mainMenuHandler.end()){
            //如果找不到相应的函数，则代表输入错误，要重新输入
            std::cerr<<"invalid input! Please input again: "<<endl;
            continue;
        }


        //查找到指令响应的函数则直接运行该函数
        iter->second();
        
    }
}





void ClientService::showLoginMenu()const{
    //显示菜单：登录、注册、退出等
    fflush(stdin);
    cout<<"==============="<<endl;
    cout<<"\t1.Login"<<endl;
    cout<<"\t2.Register"<<endl;
    cout<<"\t3.Quit"<<endl;
    cout<<"==============="<<endl;
    cout<<"Please input your choice(1、2、3): ";
        
}

//登录功能
void ClientService::login(){
    string s_id;
    int id=0;
    string password;
    
    cout<<"UserID: ";
    cin>>s_id;
    cin.ignore(255,'\n');

    cout<<"Passowrd: ";
    cin>>password;
    cin.ignore(255,'\n');

    try{
        id=std::stoi(s_id);
    }catch(std::invalid_argument){
        //输入的id格式错误
        cerr<<"Error: id["<<s_id<<"] invalid argument! "<<endl;
        return;
    }

    //组装登录用的Json
    json js;
    js["msgid"]=int(EnMsgType::LOG_MSG);
    js["id"]=id;
    js["password"]=password;
    string request=js.dump();

    //每次登录前都初始化原子变量
    m_isLoginSuccess=false;
    
    //发送登录Json到服务器
    _conn->send(request);
    
    
    //发送成功，准备接收服务器返回过来的响应Json
    //缓冲区大小大一点，因为涉及到群组信息，成员信息，好友信息，离线消息等
    //char buffer[5120]={0};
    //读取响应数据
    //len=recv(clientfd,buffer,1024,0);

    //发送成功后等待信号量，由子线程处理完后通知是否登录成功
    sem_wait(&rwsem);

    if(m_isLoginSuccess==true){
        //登录成功
        //允许进入聊天界面循环
        isChatMenuRun=true;
        //显示主聊天菜单界面
        ChatMenu();
    }
        
        
    
}

//注册功能
void ClientService::reg(){
    
    //cin.ignore(255,'\n');

    string name;
    string password;


    cout<<"Username: ";
    cin>>name;
    cout<<"Password: ";
    cin>>password;
    cin.ignore(255,'\n');

    //组装注册业务的json
    json js;
    js["msgid"]=int(EnMsgType::REG_MSG);//注册消息类型
    js["name"]=name;
    js["password"]=password;
    string request=js.dump();
    
    //客户端向服务端发送注册Json数据
    this->_conn->send(request);
    //主线程发送注册请求后，等待信号量
    sem_wait(&rwsem);
    
}

//退出功能
void ClientService::quit(){
    cout<<"quit"<<endl;
    isMainLoopRun=false;
    sem_destroy(&rwsem);
    exit(0);
}


//聊天页面的主体
void ClientService::ChatMenu(){
    //进入主聊天页面之后，打印帮助命令
    help("");

    //获取用户输入的命令行
    string buffer;
    while(isChatMenuRun){
        //循环主体

        std::getline(cin,buffer);
        
        
        //存储用户输入的命令行（未改动过的，即包含了:）
        string commandBuff(buffer);

        //存储用户输入的功能命令（比如：help,chat...），即裁剪了前面一段
        string command;

        //解析用户输入的命令行字符串，搜索 ':' 位置
        int idx=commandBuff.find(':');
        if(idx==string::npos){
            //找不到':'则直接将命令存储
            command=commandBuff;
        }else{
            //否则就要裁剪字符串，提取输入的指令是什么
            command=commandBuff.substr(0,idx);
        }
        cout<<"your input:"<<endl;
        cout<<commandBuff<<endl;
        cout<<command<<endl;
        
        //从功能关系表中查找是否command指令对应的功能函数
        auto it=commandHandlerMap.find(command);

        //若找不到，则提示用户需要重新输入
        if(it==commandHandlerMap.end()){
            std::cerr<<"invalid input command! "<<endl;
            //清空缓冲区
            cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
            continue;
        }

        //调用function回调函数
        it->second(commandBuff.substr(idx+1,commandBuff.size()-idx));


    }//end while
}

//显示登录之后，该用户的基本信息、好友列表和群组列表
void ClientService::showCurrentUserData(){
    cout << "======================login user======================" << endl;
    cout << "current login user => id:" << m_currentUser.getId() << " name:" << m_currentUser.getName() << endl;
    cout << "----------------------friend list---------------------" << endl;
    if (!m_currentUserFriendList.empty())
    {
        for (User &user : m_currentUserFriendList)
        {
            cout << user.getId() << " " << user.getName() << " " << user.getState() << endl;
        }
    }
    cout << "----------------------group list----------------------" << endl;
    if (!m_currentUserGroupsList.empty())
    {
        for (Group &group : m_currentUserGroupsList)
        {
            cout << group.getID() << " " << group.getName() << " --- " << group.getDesc() << endl;
            cout<<"Menbers: "<<endl;
            for (GroupUser &user : group.getUsers())
            {
                cout << user.getId() << " " << user.getName() << " " << user.getState()
                     << " " << user.getRole() << endl;
            }
            cout<<"------------------------------------------------"<<endl;
        }
    }
    cout << "======================================================" << endl;
}


//处理登录的响应逻辑
void ClientService::doLoginResponse(json& responseLogin){
    /**
     * 登录的errno有三种结果：
     * 0--登录成功，返回用户基本信息，离线消息以及好友列表等
     * 1--登录失败，id或密码错误
     * 2--登录失败，所登录的账号已在线
     */
    
    if(responseLogin["errno"].get<int>()!=0){
        //登录失败，直接输出响应json里面附带的错误信息
        std::cerr<<responseLogin["errmsg"]<<endl;
        //给原子量赋值为false
        m_isLoginSuccess=false;
        return;
    }else{
        //登录成功
        
        //记录当前用户信息
        m_currentUser.setID(responseLogin["id"].get<int>());
        m_currentUser.setName(responseLogin["name"]);

        //1、记录当前用户的好友列表
        //初始化全局数组
        m_currentUserFriendList.clear();

        //通过检测响应Json是否包含["friedns"]这个键来判断用户是否有好友
        if(responseLogin.contains("friends")){
            //包含好友列表则将值（也是个Json类型）拷贝到一个string数组中
            //并二次解析成每个User对象，将每个User对象打入到用于保存好友列表的全局数组中

            vector<string> jsFriendsArr=responseLogin["friends"];
            for(string& str:jsFriendsArr){
                json tempJson=json::parse(str);
                User user;
                user.setID(tempJson["id"].get<int>());
                user.setName(tempJson["name"]);
                user.setState(tempJson["state"]);
                
                //逐个将用户信息保存到全局数组中
                m_currentUserFriendList.push_back(user);
            }
        }

        //2、同样的，我们还要记录群组列表的信息

        //全局数组初始化
        m_currentUserGroupsList.clear();

        if(responseLogin.contains("groups")){
            vector<string> jsGroupsArr=responseLogin["groups"];
            for(string& str:jsGroupsArr){
                json tempJson=json::parse(str);
                Group group;
                group.setID(tempJson["groupid"].get<int>());
                group.setName(tempJson["groupname"]);
                group.setDesc(tempJson["groupdesc"]);

                //记录该群中成员列表的信息
                vector<string> usersInEachGroup=tempJson["users"];
                for(string& userStr: usersInEachGroup){
                    GroupUser user;
                    //将群组成员列表里面的用户逐个解析
                    json userInGroupJson=json::parse(userStr);

                    user.setID(userInGroupJson["id"].get<int>());
                    user.setName(userInGroupJson["name"]);
                    user.setState(userInGroupJson["state"]);
                    user.setRole(userInGroupJson["role"]);

                    //解析好后放入当前群组对象的成员列表数组中
                    group.getUsers().push_back(user);
                }
                //一个群组对象解析好后，将该群组对象放入记录群组的全局数组中
                m_currentUserGroupsList.push_back(group);
            }
        }
        //3、显示当前登录用户的基本信息
        showCurrentUserData();

        //4、显示离线消息
        if(responseLogin.contains("offlinemsg")){
            //有离线消息的话则直接输出
            vector<string> offlineMsgJsons=responseLogin["offlinemsg"];
            for(string& msgJsons: offlineMsgJsons){
                cout<<msgJsons<<endl;
                json curMsgJson=json::parse(msgJsons);
                //直接输出离线消息
                //在数据库中存储离线信息的内容是id对应聊天消息体json，json中即包括时间等信息
                //所以这里可以调用时间出来
                //格式为：time + [id] + name + " said: " + XXX
                cout<<curMsgJson["time"].get<string>()<<" ["<<curMsgJson["id"]<<"] "<<curMsgJson["name"]
                    <<" said: "<<curMsgJson["msg"].get<string>()<<endl;
            }
        }

        //登录成功给原子量赋值为true
        m_isLoginSuccess=true;
    }//end 登录成功后的操作
}


void ClientService::doRegResponse(json& responseJson){

    //接收注册响应数据成功，准备解析响应Json
    if(responseJson["errno"].get<int>()!=0){
        //响应数据注册码表示注册失败
        std::cerr<<"register error, is already exist! "<<endl;

    }else{
        //注册成功，返回用户id，并提示用户要牢记（物理记住）
        std::cout<<"registered success, userid is: "<<responseJson["id"]<<" , do not forget it!"<<endl;
    }

}


//获取系统时间（聊天时需要添加时间信息）
string ClientService::getCurrentTime(){
    auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    struct tm*ptm=localtime(&tt);
    char date[60]={0};
    sprintf(date,"%d-%02d-%02d %02d:%02d:%02d",
            (int)ptm->tm_year+1900,(int)ptm->tm_mon+1,(int)ptm->tm_mday,
            (int)ptm->tm_hour,(int)ptm->tm_min,(int)ptm->tm_sec);
    return std::string(date);
}


#pragma region 各功能函数的实现

// 帮助提示函数，相当于控制台常见到的 xxx -help
void ClientService::help(string str = ""){
    cout<<"show command list >>> \n";
    for(auto& comm:commandMap){
        cout<<comm.first<<" : "<<comm.second<<endl;
    }
    cout<<endl;
}

// 聊天功能命令 chat:friendid:message
void ClientService::chat(string str){//此时str只剩下friendid:message
    
    int idx=str.find(':');
    if(idx==string::npos){
        std::cerr<<"chat command invalid! "<<endl;
        return;
    }
    //获取聊天好友（目标）id
    int friendid=atoi(str.substr(0,idx).c_str());

    //获取聊天消息正文内容
    string message=str.substr(idx+1,str.size()-idx);

    //封装发送信息的json
    json js;
    js["msgid"]=int(EnMsgType::ONE_CHAT_MSG);
    js["id"]=m_currentUser.getId();
    js["name"]=m_currentUser.getName();
    js["toid"]=friendid;
    js["msg"]=message;
    js["time"]=getCurrentTime();

    string buffer(js.dump());
    this->_conn->send(buffer);
    

}

// 添加好友命令 addfriend:friendid
void ClientService::addfriend(string str){
    //获取好友id，由于在mainMenu中str已经给截取了前面的':'
    //此时剩下命令只有后面的friendid了
    int friendid=atoi(str.c_str());
    cout<<"add frined :"<<friendid<<endl;
    json js;
    js["msgid"]=int(EnMsgType::ADD_FRIEND_MSG);
    js["id"]=m_currentUser.getId();
    js["friendid"]=friendid;

    string buffer(js.dump());

    //发送js
    this->_conn->send(buffer);


}

// 创建群组命令 creategroup:groupname:groupdesc
void ClientService::creategroup(string str){
    //按照':'拆分字符串
    int idx=str.find(':');
    if(idx==string::npos){
        //格式错误
        std::cerr<<"creategroup command invalid! "<<endl;
        return;
    }

    string groupName=str.substr(0,idx);
    string groupDesc=str.substr(idx+1,str.size()-idx);

    json js;
    js["msgid"]=int(EnMsgType::CREATE_GROUP_MSG);
    js["id"]=m_currentUser.getId();
    js["groupname"]=groupName;
    js["groupdesc"]=groupDesc;

    string buffer(js.dump());
    this->_conn->send(buffer);

}

// 加入群组命令 addgroup:groupid
void ClientService::addgroup(string str){
    //因为命令去掉冒号后只剩一个groupid了，所以这里直接转int
    int groupid=atoi(str.c_str());
    int userid=m_currentUser.getId();

    json js;
    js["msgid"]=int(EnMsgType::ADD_GROUP_MSG);
    js["id"]=userid;
    js["groupid"]=groupid;

    string buffer(js.dump());
    this->_conn->send(buffer);

}

// 群组聊天命令 groupchat:groupid:message
void ClientService::groupchat(string str){
    //截取':'
    int idx=str.find(':');
    if(idx==-1){
        std::cerr<<"groupchat command error! "<<endl;
        return;
    }

    int groupid=atoi(str.substr(0,idx).c_str());
    string chatMsg=str.substr(idx+1,str.size()-idx);

    json js;
    js["msgid"]=int(EnMsgType::GROUP_CHAT_MSG);
    js["id"]=m_currentUser.getId();//说话人id
    js["name"]=m_currentUser.getName();
    js["groupid"]=groupid;
    js["msg"]=chatMsg;
    js["time"]=getCurrentTime();//说话时间

    string buffer(js.dump());
    this->_conn->send(buffer);

}

// 退出账号命令 loginout
void ClientService::loginout(string str){
    int userid=m_currentUser.getId();

    json js;
    js["msgid"]=int(EnMsgType::LOGINOUT_MSG);
    js["id"]=userid;

    string buffer(js.dump());
    this->_conn->send(buffer);
    isChatMenuRun=false;
    

}

#pragma endregion   各功能函数的实现


//!该函数只能给IO线程用，主线程不要用它！
//客户端的IO线程收到服务端发来消息，IO线程解析消息类型，并且输出处理相关消息
void ClientService::IOparseMsgType(json& js){
    //获取消息类型
    EnMsgType msgType=EnMsgType(js["msgid"].get<int>());

    switch(msgType){
        case EnMsgType::ONE_CHAT_MSG:{
            //如果该消息是点对点聊天消息，则输出
            cout<<js["time"].get<string>()<<" ["<<js["id"]<<"] "<<js["name"].get<string>()
                <<" said: "<<js["msg"].get<string>()<<endl;
            break;
        }

        //创建群组操作后的响应
        case EnMsgType::CREATE_GROUP_ACK:{
            int errid=js["errno"].get<int>();
            if(errid==0){
                //创建群组成功，输出groupid
                cout<<"create group success! groupid: "<<js["groupid"].get<int>()<<endl;
            }else{
                //创建群组失败
                cout<<js["errmsg"]<<endl;
            }
            break;
        }

        //加入群组功能响应
        case EnMsgType::ADD_GROUP_ACK:{
            int errid=js["errno"].get<int>();
            if(errid==0){
                //加入群组成功，输出groupid
                cout<<"user: "<<js["userid"].get<int>()<<" join group success! groupid: "<<js["groupid"].get<int>()<<endl;
            }else{
                //加入群组失败
                cout<<js["errmsg"]<<endl;
            }
            break;
        }

        //群组聊天消息
        case EnMsgType::GROUP_CHAT_MSG:{
            cout<<js["time"].get<string>()<<" Group MSG["<<js["groupid"].get<int>()<<"] - "<<js["name"].get<string>()<<"["<<js["id"]<<"] "
                <<" said: "<<js["msg"].get<string>()<<endl;
            break;
        }

        //客户端登录
        case EnMsgType::LOG_MSG_ACK:{
            doLoginResponse(js);

            //不管登录成功与否都响应信号量
            //通知主线程，由主线程作判断
            sem_post(&rwsem);
            break;
        }

        //客户端注册响应
        case EnMsgType::REG_MSG_ACK:{
            doRegResponse(js);
            sem_post(&rwsem);
            break;
        }

        default:
            break;
    }
}